#ifndef __RPHOTON_HPP__
#define __RPHOTON_HPP__

#include "../tdp2/tdp2_threading.hpp"
#include "../tdp2/tdp2_visualresources.hpp"
#include "../tdp2/tdp2_string.hpp"
#include "../tdp2/tdp2_vector.hpp"
#include "../tdp2/tdp2_linkedlist.hpp"
#include "../tdp2/tdp2_guard.hpp"

#include "../../rdx/src/rdx/rdx_objectmanagement.hpp"
#include "../rdistrolib/distributionsession.h"

using namespace TDP;
using namespace TDP::Math;
using namespace TDP::Containers;
using namespace RDX::ObjectManagement;

typedef TDP::Resources::PackedWorldVertex rpPackedVertex;
typedef TDP::Resources::WorldModel::GeometrySegmentInfo rpSegment;
typedef TDP::Resources::WorldModel::GeometryClusterInfo rpBatchGroup;
typedef TDP::Resources::WorldModel::Model rpModel;
typedef TDP::Resources::BundledGeometryData::Segment rpGSegment;
typedef TDP::Resources::BundledGeometryData::Cluster rpGCluster;

// Globals

namespace TDP
{
	namespace Resources
	{
		struct WorldModel;
	};
};

enum
{
	RP_COLLECTIONMODE_FIXED_RADIUS,
	RP_COLLECTIONMODE_FIXED_PHOTON_COUNT,
};

enum
{
	RP_CHARTMODE_COLOR,			// Simple color average
	RP_CHARTMODE_DELUXE,		// Bias, transfer to directional, transfer residual to flat
	RP_CHARTMODE_SUPERDELUXE,	// 3 basis, accumulate directional, primary light
	RP_CHARTMODE_SH,			// Bias, use spherical harmonics basis
};


#define RP_BASIS_0(type)		static_cast<type>(-0.40824829046386301636621401245098),\
								static_cast<type>(-0.70710678118654752440084436210485),\
								static_cast<type>(0.57735026918962576450914878050196)

#define RP_BASIS_1(type)		static_cast<type>(-0.40824829046386301636621401245098),\
								static_cast<type>(0.70710678118654752440084436210485),\
								static_cast<type>(0.57735026918962576450914878050196)

#define RP_BASIS_2(type)		static_cast<type>(0.81649658092772603273242802490196),\
								static_cast<type>(0),\
								static_cast<type>(0.57735026918962576450914878050196)

// SH: 8bpp per channel, 9 channels, 8bpp per exponent = 80bpp
// Superdeluxe:
// - 1: 4bpp B1 RGB, 4bpp ex1
// - 2: 4bpp B2 RGB, 4bpp ex2
// - 3: 4bpp B3 RGB, 4bpp pl
// - 4: 4bpp secondary gloss color, 4bpp secondary gloss magnitude (linear)
// - 5: 24bpp secondary gloss dir (ortho-tangent space)
// = 40bpp

static const int RP_CHARTMODE_ELEMENTS[] =
{
	3,	// RGB color
	12,	// RGB color, direction, RGB residual, RGB single
	21,	// RGB color, 3 basis colors, 1 reflective color, 1 reflective direction, 1 PL visibility, 2 padding
	30,	// 9 RGB SH coefs, RGB simplified single
};

static const int RP_MAX_CHART_ELEMENTS = 30;

// Changing coef counts requires updating a few things:
// - rpLight::Accumulate
// - RRadSampleToCoefs (need to update RRAD too)
// - MergeCoefs
// - AmplifyCoefs
// - PlaceSH (or equivalent)
// - LightmapBuildJob

static const int RP_CHARTMODE_MAX_ELEMENTS = 30;

struct rpConfig
{
	int photonsPoint;					// Photons emitted by point sources
	int photonsPer100squ;				// Photons emitted per square unit of emissive geometry
	float photonsPointScale;			// Amplifier for point emitters
	int collectionPointPhotons;			// Number of photons to collect in fixed-photon mode
	float collectionPointRadius;		// Radius for fixed-radius mode
	float maxUnbiasAmplification;		// Maximum angular bias scale
	int collectionMode;					// Mode to use for collecting photons
	float defaultLightEmissionRadius;	// Default radius of lights

	float defaultPointIntensity;
	float traceDistance;

	int maxDeflections;
	int photonsPerCatalogCluster;
	int maxPhotonsPerManifold;

	int inputSubchartSize;	// Size of subcharts

	int chartMode;

	bool alwaysDeflect;	// Deflect at 50% energy instead of 50% of the time
	bool disableTreeCull;	// Disable photon cataloging (hit the ENTIRE catalog on every collect)

	//int numThreads;
	int numAreaApproximationPoints;

	int numFinalGatherShots;

	int chartSupersampling;

	float radiosityPushoffDistance;		// Distance to push radiosity points off of the surface
	float radiosityExclusionRadius;		// Radius around rad points which must not contain geometry
	bool radiosityExportPoints;			// If true, export radiosity sample points
	bool radiosityAccumulate;			// Add samples from a .radsample file
	unsigned int radiosityGranularity;	// Grid size of radiosity points to capture

	bool castLights;	// If true, cast lights and export .directlight.  If false, import .directlight
	bool noDirect;		// If true, no direct light

	bool distribute;				// If true, use rdistro
	const char *distributionServer;
	const char *distributionKey;
	RDistro::DistributionSession::JobKey distributionJobKey;
	bool distributionSlave;			// If true, this is a slave distribution, not the original process

	RDistro::DistributionSession *distributionSession;
};


struct rpObjectProperty
{
	CString key;
	CString value;

	inline rpObjectProperty()
		: key(TDP::Memory::DefaultAllocator()),
		  value(TDP::Memory::DefaultAllocator())
	{
	}
};

struct rpObject
{
	LinkedList<rpObjectProperty> properties;

	inline rpObject()
		: properties(TDP::Memory::DefaultAllocator())
	{
	}

	inline const rpObjectProperty *GetProperty(const CString &name) const
	{
		const ListLink<rpObjectProperty> *l = properties.First();

		while(l)
		{
			if(l->DataPtr()->key == name)
				return l->DataPtr();
			l = l->Next();
		}
		return NULL;
	}

	inline const rpObjectProperty *GetProperty(const char *name) const
	{
		const ListLink<rpObjectProperty> *l = properties.First();

		while(l)
		{
			if(l->DataPtr()->key == name)
				return l->DataPtr();
			l = l->Next();
		}
		return NULL;
	}
};

// Lights
struct rpLight
{
	enum rpLightType
	{
		rpLightTypePoint_k,
		rpLightTypeSky_k,
	};

	rpLightType lightType;

	float virtualEmissionRadius;

	FVec3 direction;
	FVec3 origin;
	FVec3 emission;
	bool isSpotlight;

	bool isPrimary;
	Int32 lightIndex;		// Exported primary light index
	bool primaryNoVisibilityFalloff;
	float primaryFragmentation;

	bool CouldReachManifold(const struct rpManifold *rp) const;
	bool CouldShadowManifold(const struct rpManifold *caster, const struct rpManifold *receiver) const;
	void Accumulate(const FVec3 &location, const FVec3 &phongNormal, const FVec3 &phongSV, const FVec3 &phongTV, const FVec3 &surfaceNormal, float scalar, float *coefs,
		bool *isVisible) const;
	void GenerateClipPlanes(const Math::FTriangle3 &wst, Math::FPlane3 clipPlanes[3]) const;
	bool FacingPlane(const Math::FPlane3 &p) const;
};

struct rpPhotonImpactData
{
	float intensity;
	float biasedIntensity;
	FVec3 location;
	char direction[3];
	unsigned char color[3];
	unsigned char pad[2];

	inline FVec3 Color() const
	{
		return FVec3(color[0], color[1], color[2]) * intensity / 255.0f;
	}

	inline FVec3 BiasedColor() const
	{
		return FVec3(color[0], color[1], color[2]) * biasedIntensity / 255.0f;
	}
};

struct rpPhotonCatalogData
{
	FVec3 planeNormal;
	float planeDist;

	unsigned int numPhotons;
	unsigned int offset;
	bool isLeaf;
};

struct rpPhotonCatalog
{
	rpPhotonCatalogData data;
	SingleInstance<rpPhotonCatalog> children[2];	// 0 = front, 1 = back
};


struct rpManifold
{
	const rpSegment *seg;
	const rpBatchGroup *bg;

	CRef<rpPackedVertex> vertData;
	CRef<UShort> triData;

	UInt32 modelIndex;
	UInt32 batchGroupIndex;
	UInt32 segmentIndex;

	FVec3 cullSphereOrigin;
	float cullSphereRadius;

	Int32 primaryLightIndex;
	bool conflictingPrimaryLights;

	void CalculateCullInfo();
};

struct rpManifoldList
{
	LinkedList<rpManifold *> manifoldList;

	inline rpManifoldList()
		: manifoldList(TDP::Memory::DefaultAllocator())
	{
	}
};

struct rpChartHeader
{
	int baseX, baseY;
	int width, height;
	int chartMode;
};

extern Vector<rpManifold> manifolds;
extern Vector<rpManifoldList> faceManifolds;
extern Vector<rpManifoldList> chartManifolds;
extern rpConfig config;
extern RDX::ObjectManagement::CRef<TDP::Resources::WorldModel> geometry;


void CreateCharts(const CString &basePath,  const LinkedList<rpLight> *lights);
void CreateLightmaps(const CString &basePath);
void CastLightVolumes(const LinkedList<rpObject> *objects);
FVec3 ParseVector(const CString &str);


void MergeCoefs(const float *src, float *dest, unsigned int numClusters, const float *scalars);
void AmplifyCoefs(float *coefs, float scale);

struct rpLightVolumeTreeNode
{
	enum
	{
		flagIsLeaf_k			= 1,
		flagIsTranslucent_k		= 2,
		flagIsModelSpecific_k	= 4,	// If true, then this division only occurs for the model currently being tested
	};

	unsigned char flags;

	Math::FPlane3 splitPlane;

	SingleInstance<rpLightVolumeTreeNode> front;	// On splits, always a clone of the original leaf
	SingleInstance<rpLightVolumeTreeNode> back;		// NULL for occluders
};

struct rpLightVolume
{
	bool isPoint;
	FVec3 color;	// Direction of the
	FVec3 v;		// Direction of the volume for directional, origin for point
	SingleInstance<rpLightVolumeTreeNode> rootNode;

	void ContribAtPoint(const FVec3 &p, FVec3 &color, FVec3 &direction) const;
};

extern LinkedList<rpLightVolume> lights;

#endif
